<!-- target和currentTarget的区别是：
target：触发事件的元素。
currentTarget：事件绑定的元素。
两者在没有冒泡的情况下，是一样的值，但在用了事件委托的情况下，就不一样了； -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <!-- <link rel="icon" href="" type="image/x-icon" /> -->
</head>

<body>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    html,
    body,
    .container {
      display: flex;
      flex-direction: column;
      background: #d6a8a8;
      min-height: 100vh;
    }

    .container {
      overflow: hidden;
    }
  </style>
  <div class="container">231</div>
  <script src="">
    /*
                                                            选择题 判断多选的情况
                                                            */
    let arr1 = [
      {
        title: 'abcdefg',
        value: false,
      },
      {
        title: 'abcdef',
        value: true,
      },
      {
        title: 'abcd',
        value: true,
      },
      {
        title: 'abc',
        value: true,
      },
    ];
    let arr2 = [
      {
        title: 'abcdefg',
        value: true,
      },
      {
        title: 'abcdef',
        value: true,
      },

      {
        title: 'abcd',
        value: false,
      },
      {
        title: 'abc',
        value: false,
      },
    ];
    let ar = arr1.map((item) => item.value);
    let ar2 = arr2.map((item) => item.value);
    console.log(ar);
    console.log(ar2);
    let q = 0;
    let l = 0;
    let c = 0;
    function isTrue(ar, ar2) {
      let flag = false;
      for (i in ar) {
        if (ar[i] < ar2[i]) {
          flag = true;
          break;
        }
      }
      return flag;
    }
    let res111 = ar2.find((item, index) => {
      return item > ar[index];
    });
    let res11 = ar2.some((item, index) => {
      return ar.includes(item);
    });
    console.log(isTrue(ar, ar2));
    console.log(res111);
    console.log(res11);
    if (ar.toString() == ar2.toString()) {
      q++;
    } else if (res111) {
      c++;
    } else {
      l++;
    }
    // console.log('quandui--- ' + q, 'loule--- ' + l, 'cuoel--- ' + c);

    //*********************************//********************************//
    /*
      构造函数寄生继承
      js继承是构造函数原型的继承 而不是改变构造函数
      child.prototype.__proto__ = father.prototype
      child.prototype.constructor =child
    */
    function User(name, age) {
      this.name = name;
      this.age = age;
    }
    User.prototype.onShow = function () {
      console.log(this.name, this.age);
    };
    function myExtend(child, father) {
      child.prototype = Object.create(father.prototype);
      Object.defineProperty(child, 'constructor', {
        value: child,
        enumerable: false,
      });
    }

    function Admin(...args) {
      User.apply(this, args);
    }
    myExtend(Admin, User);
    const ad = new Admin('llq', 24);
    console.log('ad', ad);

    /*
     *  手写题！
     */

    // 1. Promise
    // let p = new Promise((resolve, reject) => {
    //   resolve(1)
    // })
    // p.then((i) => {},() => {})
    class Pro {
      constructor(executor) {
        this.status = 'pending';
        this.value = null;
        this.callbacks = [];
        try {
          executor(resolve.bind(this), reject.bind(this));
        } catch (error) {
          this.reject(error);
        }
      }

      resolve(value) {
        if (this.status === 'pending') {
          this.value = value;
          this.status = 'resolve';
          setTimeout(() => {
            this.callbacks.forEach((item) => {
              item.onResolve && item.onResolve(this.value);
            });
          });
        }
      }
      reject(value) {
        if (this.status === 'pending') {
          this.value = value;
          this.status = 'reject';
          setTimeout(() => {
            this.callbacks.forEach((item) => {
              item.onReject && item.onReject(this.value);
            });
          });
        }
      }
      //then方法返回一个Promise
      then(onResolve, onRreject) {
        let callFn = (type, resolve, reject) => {
          let result = type(this.value);
          // then手动返回的Promise不能与上一次相同
          if (thenPro == result) {
            throw new TypeError('Chaining cycle detected for promise');
          }
          try {
            if (result instanceof Pro) {
              // result.then(resolve,reject) //是下面的简写方式
              result.then(
                (v) => {
                  resolve(v);
                },
                (r) => {
                  reject(r);
                }
              );
            } else {
              resolve(result);
            }
          } catch (error) {
            reject(error);
          }
        };
        let thenPro = new Pro((resolve, reject) => {
          if (typeof resolve !== 'function') {
            resolve = (value) => value;
          }
          if (typeof reject !== 'function') {
            resolve = (value) => value;
          }
          if (this.status === 'resolve') {
            setTimeout(() => {
              callFn(onResolve, resolve, reject);
            });
          }
          if (this.status === 'reject') {
            setTimeout(() => {
              callFn(onReject, resolve, reject);
            });
          }
          if (this.status === 'pending') {
            this.callbacks.push({
              onResolve: () => {
                callFn(onResolve, resolve, reject);
              },
              onRreject: () => {
                callFn(onRreject, resolve, reject);
              },
            });
          }
        });
        return thenPro;
      }
      catch(onRreject) {
        return this.then(undefined, onRreject);
      }

      static resolve(value) {
        return new Pro((resolve, reject) => {
          try {
            if (value instanceof Pro) {
              value.then(
                (v) => {
                  resolve(v);
                },
                (r) => {
                  reject(r);
                }
              );
            } else {
              resolve(value);
            }
          } catch (error) {
            reject(error);
          }
        });
      }
      static reject(value) {
        return new Pro((resolve, reject) => {
          reject(value);
        });
      }

      static race(proArr) {
        return new Pro((resolve, reject) => {
          proArr.forEach((item, idnex) => {
            item.then(
              (v) => {
                resolve(v);
              },
              (r) => {
                reject(r);
              }
            );
          });
        });
      }

      static all(proArr) {
        let arr = [];
        return new Pro((resolve, reject) => {
          proArr.forEach((item, index) => {
            item.then(
              (v) => {
                arr[index] = v;
                if (arr.length === proArr.length) {
                  resolve(arr);
                }
              },
              (r) => {
                reject(r);
              }
            );
          });
        });
      }
    }

    //call
    //  fn.myCall(obj)  =>  obj.fn()
    Function.prototype.myCall = function (context, ...rargsest) {
      //this 是调用的函数(普通函数是Function的实例 Function.prototype方法中的this指向实例函数),     函数基于__proto__ 找到  Function.prototype
      const keyFn = Symbol('keyFn');
      context[keyFn] = this;
      let result = context[keyFn](...args);
      // context.fn = this;
      // let result = context.fn(...args);
      // delete context.fn;
      delete context[keyFn];
      return result;
    };

    //apply
    Function.prototype.myApply = function (context, args) {
      context.fn = this;
      let res;
      if (!args) {
        res = context.fn();
      } else {
        res = context.fn(...args);
      }
      delete context.fn;
      return res;
    };

    // 手写bind函数
    Function.prototype.myBind = function (context, ...args) {
      const thisFn = this; //当前调用的函数
      return function (...params) {
        return thisFn.apply(context, [...args, ...params]);
      };
    };
    const bind =
      (fn, context, ...boundArgs) =>
        (...args) =>
          fn.apply(context, [...boundArgs, ...args]);
    const call =
      (key, ...args) =>
        (context) =>
          context[key](...args);

    //手写new
    function myNew(fun, ...args) {
      // let obj = Object.create(fun.prototype)
      //创建新对象并把新对象的原型指向构造函数的原型，将构造函数的this指向新对象 并且调用
      let obj = {};
      obj.__proto__ = fun.prototype;
      let res = fun.apply(obj, args);
      return res instanceof Object ? res : obj;
    }
    //深克隆对象
    function deepClone(obj) {
      // 先判断是对象还是数组
      let copy = obj instanceof Array ? [] : {};
      for (let key in obj) {
        // 判断是否是对象上的属性，而不是原型上的属性
        if (obj.hasOwnProperty(key)) {
          // obj[key] 是否是对象，如果是对象，递归遍历
          copy[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
        }
      }
      return copy;
    }

    const deepClone1 = (obj) => {
      let copy = obj instanceof Array ? [] : {};
      for ([k, v] of Object.entries(obj)) {
        if (obj.hasOwnProperty(k)) {
          copy[k] = typeof v === 'object' ? deepClone1(k) : v;
        }
      }
      return copy;
    };
    //  手写 instanceof1
    function checkPrototype(obj, classFn) {
      if (!obj.__proto__) return false;
      if (obj.__proto__ == classFn.prototype) return true;
      return checkPrototype(obj.__proto__, classFn);
    }
    //  手写 instanceof2
    const myInstanceof = (obj, classFn) => {
      let point = obj.__proto__;
      // let point = Object.getPrototypeOf(obj)
      while (point) {
        //while循环用在循环次数没有明确的情况下
        if (point == classFn.prototype) return true;
        point = point.__proto__;
        // point = Object.getPrototypeOf(obj)
      }
      return false;
    };

    //1 将数组拆分成平均长度的数组
    function group(array, subNum) {
      let startIndex = 0;
      let newArray = [];
      while (startIndex < array.length) {
        newArray.push(array.slice(startIndex, (startIndex += subNum)));
        // 1.newArray.push(array.slice(startIndex, startIndex + subNum));
        // startIndex+=subNum
        // 2.newArray.push(array.slice(startIndex*subNum, (startIndex +1)*subNum));
        // (page-1*size),(page*size)
      }
      return newArray;
    }
    //2 将数组拆分成平均长度的数组
    function group2(array, subNum) {
      console.log(array);
      let newArray = [];
      for (let i = 0; i < array.length; i++) {
        // newArray.push(array.slice(i, (i += subNum)));
        newArray.push(array.slice(i, i + subNum));
        i = i + subNum - 1;
        // newArray.push(array.slice(i * subNum, (i + 1) * subNum));
      }
      return newArray;
      // return newArray.filter((item) => item.length);
    }
    // console.log(group2([1, 2, 3, 45, 7, 8, 9, 75, 5, 4], 3));

    // 深度过滤数组重复值
    const uniqueDeep = (arr) => {
      let tempArr = [];
      let obj = {};
      arr.forEach((item) => {
        if (typeof item !== 'object') {
          if (!tempArr.includes(item)) {
            tempArr.push(item);
          }
        } else {
          let key = JSON.stringify(item);
          if (!obj[key]) {
            tempArr.push(item);
            obj[key] = 1;
          }
        }
      });
      return tempArr;
    };
    // 深度过滤数组重复值
    const uniqueDeepMap = (array) => {
      const res = [];
      const map = new Map();
      for (let i = 0, len = array.length; i < len; i++) {
        const item = array[i];
        if (!map.has(item)) {
          res.push(item);
          map.set(item, true);
        }
      }
      return res;
    };

    // 深度过滤任意数组重复
    const unique = (arr) => {
      let result = [];
      const equals = (a, b) => {
        if (a === b) return true;
        if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
        if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
        if (a === null || a === undefined || b === null || b === undefined) return false;
        if (a.prototype !== b.prototype) return false;
        let keys = Object.keys(a);
        if (keys.length !== Object.keys(b).length) return false;
        return keys.every((k) => equals(a[k], b[k]));
      };
      for (let i = 0, len = arr.length; i < len; i++) {
        let isFinded = false;
        for (let k = 0; k < result.length; k++) {
          if (equals(arr[i], result[k])) {
            isFinded = true;
            break;
          }
        }
        if (!isFinded) {
          result.push(arr[i]);
        }
      }
      return result;
    };

    //统计出现最多次数的字符串
    const llq = (str) => {
      let temp = {};
      let max = 0,
        val;
      for (let item of str) {
        if (!temp[str]) {
          temp[str] = 1;
        } else {
          temp[str]++;
        }
      }
      for (let k in temp) {
        if (temp[k] > max) {
          temp[k] = max;
          val = k;
        }
      }
      return {
        val,
        max,
      };
    };
    //统计出现连续最多次数的字符串
    const llq1 = (str) => {
      let j = 1,
        i = 0,
        max = 0,
        val;
      // for (let i = 1; i < str.length - 1; i++) {
      //   if (str[i] !== str[j]) {
      //     j = i;
      //     if (j - i > max) {
      //       max = j - i;
      //       val = str[i];
      //     }
      //   }
      //
      // }
      while (i < str.length - 1) {
        if (str[i] !== str[j]) {
          i = j;
          if (j - i > max) {
            max = j - i;
            val = str[i];
          }
        }
        j++;
      }
      return {
        max,
        val,
      };
    };
    //统计出现连续最多次数的字符串
    const totalStrCount = () => {
      let max = 0;
      let objKey = '';
      let str = 'fdsgfdgfasfda';
      Object.entries(
        [...str].reduce((prev, cur) => {
          if (!prev[cur]) {
            prev[cur] = 1;
          } else {
            prev[cur]++;
          }
          return prev;
        }, {})
      ).forEach(([k, v]) => {
        console.log(k, v);
        if (v > MAX) {
          max = v;
          objKey = k;
        }
      });
      return { max, objKey };
    };

    // 以下函数返回 min（包含）～ max（包含）之间的数字：
    function getRndInteger(min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    // 以下函数返回 min（包含）～ max（不包含）之间的数字：
    function getRndIntegerNo(min, max) {
      return Math.floor(Math.random() * (max - min)) + min;
    }

    // 数组随机排序
    function randomSort(arr) {
      return arr.sort(() => Math.random() - 0.5);
    }

    // 真正随机打乱数组
    function shuffleRandomArray(arr) {
      let i = arr.length;
      while (i) {
        let j = Math.floor(Math.random() * i--);
        [arr[j], arr[i]] = [arr[i], arr[j]];
      }
      return arr;
    }

    function isSplitWaterway() {
      //原本的航道数组
      let arr = [];
      // 存储的航道数组
      let pathArr = [];
      // 存储的航道每一段线段
      let lineArr = [];
      for (let i = 0; i < arr.length; i++) {
        // 第一次线段为0时直接加入第一个点
        if (lineArr.length === 0) {
          lineArr.push([arr[i].navaid.location.longitude, arr[i].navaid.location.latitude]);
        } else {
          if (arr[i - 1].navaid.radius < this.getDistance(arr[i - 1].navaid, arr[i].navaid)) {
            pathArr.push(lineArr);
            // 清空数组,重新开始压入
            lineArr = [];
            // 从当前断开的端点重新压入线段
            i--;
          } else {
            // 两点可以连接 直接将当前点压入数组
            lineArr.push([arr[i].navaid.location.longitude, arr[i].navaid.location.latitude]);
          }
        }
      }
      // 遍历完成后,检查lineArr压入pathArr
      if (lineArr.length) pathArr.push(lineArr);
      return pathArr;
    }
    //线程随眠
    function sleepss(delay) {
      let start = +new Date();
      while (+new Date() - start < delay) {
        continue;
      }
    }
    function sleepPro(delay) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, delay);
      });
    }
    //取数组中的最大值
    function arrayMax(array) {
      return array.reduce((max, elem) => (max > elem ? max : elem), array[0]);
    }
    //取数组中的最大值
    const total = [1, 2, 3, 4, 5].reduce((pre, cur) => {
      let tol = pre + cur;
      return tol;
    });
    //数组中的累加合
    const totals = [1, 2, 3, 4, 5].reduce((pre, cur) => pre + cur);

    const debounce = (fn, ms = 200) => {
      let timeoutId;
      return function (...args) {
        if (timeoutId) clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), ms);
      };
    };

    //节流函数
    function throttle(fn, wait = 150, isFirst = true) {
      let timer = null;
      let flag = true;
      return function (...args) {
        //let context = this;  这儿的this指向DOM调用者   // self 永远指向window
        if (flag && isFirst) {
          flag = false;
          fn.apply(this, args);
        } else {
          if (!timer) {
            timer = setTimeout(function () {
              fn.apply(this, args);
              timer = null;
            }, wait);
          }
        }
      };
    }
    function throttle1(fn, delay = 100) {
      let lastTime = 0;
      return function () {
        let currTime = +new Date();
        if (currTime - lastTime > delay) {
          fn.apply(this, arguments);
          lastTime = currTime;
        }
      };
    }
    const throttle2 = (fn, wait) => {
      let inThrottle, lastFn, lastTime;
      return function () {
        const context = this,
          args = arguments;
        if (!inThrottle) {
          fn.apply(context, args);
          lastTime = Date.now();
          inThrottle = true;
        } else {
          clearTimeout(lastFn);
          lastFn = setTimeout(function () {
            if (Date.now() - lastTime >= wait) {
              fn.apply(context, args);
              lastTime = Date.now();
            }
          }, Math.max(wait - (Date.now() - lastTime), 0));
        }
      };
    };

    /*********封装localStorage********/
    const setLocalStorage = (key, value) => {
      let data = localStorage.getItem('projectName');
      if (!data) {
        data = {};
      } else {
        data = JSON.parse(data);
      }
      data[key] = value;
      localStorage.setItem('projectName', JSON.stringify(data));
    };
    const getLocalStorage = (key) => {
      let data = localStorage.getItem('projectName');
      if (!data) {
        return {};
      }
      data = JSON.parse(data);
      return data[key] || {};
    };
    //拉平数组
    const deepFlat = (arr) => {
      let tempArr = [];
      arr.forEach((item) => {
        if (Array.isArray(item)) {
          tempArr.push(...deepFlat(item));
        } else {
          tempArr.push(item);
        }
      });
      return tempArr;
    };
    [[], []].flat(Infinity);

    /*****生成树状结构****/
    let treeList = [
      { id: '1', text: '一级菜单1', parentId: null },
      { id: '2', text: '一级菜单2', parentId: null },
      { id: '3', text: '一级菜单3', parentId: null },
      { id: '2-1', text: '二级菜单1', parentId: '1' },
      { id: '2-2', text: '二级菜单2', parentId: '2' },
      { id: '2-3', text: '二级菜单3', parentId: '2' },
      { id: '3-2-1', text: '三级菜单1', parentId: '2-1' },
    ];
    //数组转成树  (通过先处理最父级id)
    function getTreeList(list, iid) {
      let newArray = [];
      list.forEach((item, index) => {
        if (item.parentId === iid) newArray.push(item);
      });
      newArray.forEach((item, index) => {
        item.children = getTreeList(treeList, item.id);
        if (item.children.length === 0) delete item.children;
      });
      return newArray;
    }

    // getTreeList(treeList,null)
    //数组转成树 (通过id键名找到以前数组的每一项)
    const arrayToTree = (items) => {
      const result = [];
      const itemMap = {}; // 有时候多层遍历循环（递归） 可以考虑把数组转化成map对象通过id键名找到以前数组的每一项 而不是通过遍历
      for (const item of items) {
        const id = item.id;
        const pid = item.parentId;
        if (!itemMap[id]) {
          itemMap[id] = {
            ...item,
            children: [],
          };
        }
        const treeItem = itemMap[id];
        if (pid == null) {
          result.push(treeItem);
        } else {
          if (!itemMap[pid].children) itemMap[pid].children = [];
          itemMap[pid].children.push(treeItem);
          if (itemMap[pid].children.length === 0) delete itemMap[pid].children;
        }
      }
      return result;
    };

    // (通过每项父id键名找到所遇父id的数组)
    function getTrees(list, parentId) {
      const items = {};
      for (let i = 0; i < list.length; i++) {
        let pid = list[i].parentId;
        if (items[pid]) {
          items[pid].push(list[i]);
        } else {
          items[pid] = [];
          items[pid].push(list[i]);
        }
      }
      function formatTree(items, parentId) {
        let result = [];
        if (!items[parentId]) {
          return result;
        }
        items[parentId].forEach((its) => {
          its.children = formatTree(items, its.id);
          if (item.children.length === 0) delete its.children;
          result.push(t);
        });
        return result;
      }
      return formatTree(items, parentId);
    }

    function flatten1(tree) {
      let arr = [];
      tree.forEach((item) => {
        const { children = [], ...props } = item;
        // 添加除了children的属性
        arr.push(props);
        if (children?.length) {
          // 递归将所有节点加入到结果集中
          arr.push(...flatten(children));
        }
      });
      return arr;
    }

    // let stss = '10000000000';
    // const newStss = stss.replace(/(?=\B(\d{3})+$)/g, ','); //把金额以三位分割
    // Number.toLocaleString
    // 深度解析obj的路径
    const parsePath = (path, obj) => {
      let tempArr = path.split('.');
      let i = 0;
      while (i < tempArr.length) {
        obj = obj[tempArr[i]];
      }
      return obj;
    };

    let array1 = [1, 5, 48, 4, 8, 48, 2, 0];
    let array2 = [1, 5, 13, 4, 76, 2, 78];
    //交集
    let jiao = array1.filter((item) => new Set(array2).has(item));
    //并集
    let bing = [...new Set([...array1, ...array2])];
    //补集
    let bu = array1.filter((item) => !new Set(array2).has(item));

    // 判断给定的日期是否周六周日
    const isWeekday = (date) => [0, 1].includes(date.getDay() / 6);

    //封装的本地存储
    class LocalStorage {
      //实例
      _instance = null;
      //设置该项目的本地存储名称
      projectName = null;
      // 本地存储数据
      #data = {};
      // 初始化统一的过期时间
      express = null;
      // 单例模式获取实例
      static Instance(projectName, express) {
        if (!this._instance) {
          return (this._instance = new LocalStorage(projectName, express));
        } else {
          return this._instance;
        }
      }
      constructor(projectName, express) {
        this.projectName = projectName;
        this.express = express;
        this.#data = localStorage.getItem(this.projectName);
        this.#data = this.#data ? JSON.parse(this.#data) : {};
      }

      /**
       * 设置本地存储ey
       * @param {*string} key 键
       * @param {*any} value 值
       * @param {*number|function} express 过期时间或回调函数
       * @param {*function} cb 回调函数
       */
      setValue(key, value, express, cb) {
        if (typeof express === 'function') [cb, express] = [express, cb];
        const cTime = Date.now();
        this.#data[key] = {
          cTime,
          value,
          express: express || this.express,
        };
        localStorage.setItem(this.projectName, JSON.stringify(this.#data));
        cb && cb.call(this, this);
      }

      /**
       * 获取本地存储ey
       * @param {*string} key 键
       * @param {*function} cb 回调函数
       */
      getValue(key, cb) {
        const nTime = Date.now();
        if (this.#data[key] && this.#data[key]?.express && this.#data[key].express < nTime - this.#data[key]?.cTime) {
          return this.#data[key];
          cb && cb.call(this, this);
        } else {
          delete this.#data[key];
          cb && cb.call(this, this);

          return null;
        }
      }
      /**
       * 删除本地存储ey
       * @param {*string} key 键
       * @param {*function} cb 回调函数
       */
      delValue(key, cb) {
        if (!key) {
          this.#data = {};
          localStorage.clear();
          cb && cb.call(this, this);

          return;
        }
        this.#data[key] && delete this.#data[key];
        cb && cb.call(this, this);
      }
    }

    //随机生成字符串＋数字 Math.random().toString(36).slice(2)
    //console.log(Math.random().toString(36).slice(2));

    /*
                                              *  window.getComputedStyle()
                                              //element.getBoundingClientRect() 返回一个元素在可视区域的位置，比如：
                                              // {
                                              //   bottom: 556.21875,
                                              //   height: 393.59375,
                                              //   left: 333,
                                              //   right: 1017,
                                              //   top: 162.625,
                                              //   width: 684
                                              // }
                                              */

    /*
     *  vuex（vue-router）原理 插件install方法中
     *  调用了一下applyMixin方法，现在的版本其实就是调用了Vue.mixin，在所有组件的 beforeCreate生命周期注入了设置this.$store这样一个对象。
     */

    /**   base64 转 二进制文件(blob)
     * dataURL to blob, ref to https://gist.github.com/fupslot/5015897
     * @param dataURI
     * @returns {Blob}
     */
    function dataURItoBlob(dataURI) {
      let byteString = window.atob(dataURI.split(',')[1]);
      let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
      let ab = new ArrayBuffer(byteString.length);
      let ia = new Uint8Array(ab);
      for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      return new Blob([ab], { type: mimeString });
    }

    /**
     * base64 转 Blob 格式 和file格式
     */
    function base64UrlToBlob(urlData) {
      const arr = urlData.split(',');
      const mime = arr[0].match(/:(.*?);/)[1]; // 去掉url的头，并转化为byte
      const bstr = window.atob(arr[1]); // 处理异常,将ascii码小于0的转换为大于0
      let n = bstr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      // 转blob // return new Blob([u8arr], {type: mime})
      const filename = Date.parse(new Date()) + '.jpg';
      // 转file
      return new File([u8arr], filename, { type: mime });
    }
    //将base64转换为文件
    function dataURLtoFile(dataurl, filename) {
      let arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = window.atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new File([u8arr], filename, { type: mime });
    }
    // base64 转 二进制文件Blob
    function dataURI64toBlob(base64Data) {
      //console.log(base64Data);//data:image/png;base64,
      let byteString;
      if (base64Data.split(',')[0].indexOf('base64') >= 0) byteString = window.atob(base64Data.split(',')[1]);
      //base64 解码
      else {
        byteString = unescape(base64Data.split(',')[1]);
      }
      let mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0]; //mime类型 -- image/png

      // let arrayBuffer = new ArrayBuffer(byteString.length); //创建缓冲数组
      // let ia = new Uint8Array(arrayBuffer);//创建视图
      let ia = new Uint8Array(byteString.length); //创建视图
      for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      let blob = new Blob([ia], {
        type: mimeString,
      });
      return blob;
    }
    // .blob流 - File文件转换为base64
    //       readAsBinaryString file 将文件读取为二进制码
    // readAsDataURL file 将文件读取为 DataURL
    // readAsText file, [encoding] 将文件读取为文本
    // readAsArrayBuffer(blob)  ArrayBuffer对象用来表示通用的、固定长度的原始二进制数据缓冲区。

    // “Blob(Binary Large Object)表示二进制类型的大对象
    // let hero = { name: 'Batman' };
    // let blobObject = new Blob([JSON.stringify(hero, null, 2)], { type: 'application/json' });
    // console.log(blobObject);
    /*Blob对象*/
    // new Blob( array, options );
    // array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ，或者其他类似对象的混合体，它将会被放进 Blob。DOMStrings会被编码为UTF-8。
    // options 是一个可选的BlobPropertyBag字典，它可能会指定如下两个属性：
    // type，默认值为 ""，它代表了将会被放入到blob中的数组内容的MIME类型。
    // endings，默认值为"transparent"，用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个： "native"，代表行结束符会被更改为适合宿主操作系统文件系统的换行符，或者 "transparent"，代表会保持blob中保存的结束符不变
    /*File*/
    // new File(array, name [, options])
    // File()构造函数接受三个参数。
    // array：一个数组，成员可以是二进制对象或字符串，表示文件的内容。
    // name：字符串，表示文件名或文件路径。
    // options：配置对象，设置实例的属性。该参数可选。
    // 第三个参数配置对象，可以设置两个属性。
    // type：字符串，表示实例对象的 MIME 类型，默认值为空字符串。
    // lastModified：时间戳，表示上次修改的时间，默认为Date.now()。

    function blobToDataURI(blobOrFile) {
      let reader = new FileReader();
      reader.readAsDataURL(blobOrFile);
      reader.onload = function (e) {
        const base64Url = e.target.result;
      };
    }
    // blob转File文件
    let file = new File([blob], filename, filetype);
    // Blob {size: 306788, type: "image/png"}
    // let file = new File([blob], filename, {type: contentType, lastModified: Date.now()});

    //  把文件或者blob流转成内存地址
    // URL.createObjectURL()
    // URL.revokeObjectURL()

    // js中内置的base64编码和解码。
    // let encodedData = window.btoa('Hello, world'); // 编码 en
    // let decodedData = window.atob(encodedData); // 解码  de

    //       将file转化成blob
    // 利用URL.createObjectURL()
    let $img1 = document.getElementById('img');
    file.onchange = function (e) {
      let file = e.target.files[0];
      let fileUrl = window.URL.createObjectURL(file);
      $img1.src = fileUrl;
      img.onload = function () {
        // 手动回收
        URL.revokeObjectURL(fileUrl);
      };
    };
    // 当选择图片后，生成的img src类似"blob:null/4304d4f3-c13b-43e8-83f6-8c80426520ff"，能正常显示图片。
    // 将file转化为DataURL
    利用FileReader.readAsDataURL();
    let $img = document.getElementById('img');
    file.onchange = function (e) {
      console.log(e.target.files);
      let file = e.target.files[0];
      const fr = new FileReader(file);
      fr.readAsDataURL(file);
      fr.onload = function () {
        $img.src = this.result;
      };
    };
    // canvas转为DataURL
    // canvas画出来的图片，在html中的其他地方显示。这里的方法也是可以将canvas输出为Dataurl的来放到img标签中。
    let imgSrc = canvas.toDataURL('image/png');
    // canvas.toDataURL('image/jpeg')
    // canvas转为blob对象
    // 将canvas输出为Blob对象，这样就可以像File对象一样操作它了
    canvas.toBlob(function (blobObj) {
      console.log(blobObj); //blobObj就是blob对象（类文件）
    });
    // Blob对象显示图片
    // 同上,如果拿到blob对象，亦可以使用URL.createObjectURL()来进行转换
    canvas.toBlob(function (blobObj) {
      console.log(blobObj); //blobObj就是blob对象（类文件）
      let imgSrc = window.URL.createObjectURL(blobObj);
      document.getElementById('img').src = imgSrc;
    });
    canvas.toDataURL();

    // 最后附一个下载DataURL下载方法：
    function downloadImg() {
      let aLink = document.createElement('a');
      aLink.download = 'fileName.png'; // 文件名后缀需要和dataurl表示的相同，否则可能乱码
      aLink.href = dataUrl;
      aLink.click();
    }

    // 防止网络请求多次(使用promise实现)
    function firstPromise(returnPromiseFn) {
      let promiseInstance = null;
      return function (...args) {
        if (promiseInstance) {
          return promiseInstance;
        } else {
          return (promiseInstance = returnPromiseFn.apply(this, args).finally(() => (promiseInstance = null)));
        }
      };
    }
    // 防止网络请求多次
    function onceRequest(promiseFn) {
      let p = null;
      return function (...args) {
        return p ? p : (p = promiseFn.apply(this, args).finally(() => (p = null)));
      };
    }

    //要排序的数据
    let data = [
      { chinese: '蔡司', english: 'Chase' },
      { chinese: '艾伦', english: 'Allen' },
      { chinese: '左拉', english: 'Zola' },
      { chinese: '贝克', english: 'Baker' },
      { chinese: '伯格', english: 'Berg' },
      { chinese: '菲奇', english: 'Fitch' },
      { chinese: '迪安', english: 'Dean' },
      { chinese: '厄尔', english: 'Earle' },
      { chinese: '亨利', english: 'Henry' },
    ];

    //根据汉字首字母排序
    //使用箭头函数
    //【注】localeCompare() 是js内置方法
    // data.sort((a, b) => b.chinese.localeCompare(a.chinese, 'zh')); //z~a 排序
    let gd = data.sort((a, b) => a.chinese.localeCompare(b.chinese, 'zh')); //a~z 排序
    // console.log(data);

    //根据英文排序 比较 首字母ASCLL码
    //// console.log(data[0].english.charCodeAt(0));
    // data.sort((a, b) => b.english.charCodeAt(0) - a.english.charCodeAt(0)); //z~a 排序
    // data.sort((a, b) => a.english.charCodeAt(0) - b.english.charCodeAt(0)); //a~z 排序

    // crypto-js 数据加密

    export function formatDate(time = new Date(), cFormat) {
      if (arguments.length === 0) return null;
      const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}';
      let date;
      if (typeof time === 'object') {
        date = time;
      } else {
        if (('' + time).length === 10) time = Number.parseInt(time, 10) * 1000;
        date = new Date(time);
      }
      const formatObj = {
        y: date.getFullYear(),
        m: date.getMonth() + 1,
        d: date.getDate(),
        h: date.getHours(),
        i: date.getMinutes(),
        s: date.getSeconds(),
        a: date.getDay(),
      };

      const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
        console.log('result', result);
        console.log('key', key);
        let value = formatObj[key];
        if (key === 'a') {
          return ['一', '二', '三', '四', '五', '六', '日'][value - 1];
        }
        if (result.length > 0 && value < 10) {
          value = '0' + value;
        }
        return value || 0;
      });
      return timeStr;
    }

    // import { JSEncrypt } from 'jsencrypt';
    const PUBLIC_KEY = 'gsdgsdhs';
    /**
     * RSA加密(只能对字符串进行加密解密)
     * @param {String} value
     */
    function RsaEncrypt(value) {
      const encrypt = new JSEncrypt();
      encrypt.setPublicKey(PUBLIC_KEY);
      return encrypt.encrypt(value);
    }
    /**
     * RSA解密
     * @param value
     * @returns
     */
    export const RsaDecrypt = (value) => {
      const encrypt = new JSEncrypt();
      encrypt.setPrivateKey(PUBLIC_KEY);
      return encrypt.decrypt(value);
    };

    // import * as crypto from 'crypto-js';

    //加密 (只能对字符串进行加密解密)
    export const cryptoWord = (data) => {
      const keyHex = crypto.enc.Utf8.parse('WDA20201111');
      return crypto.DES.encrypt(data, keyHex, {
        mode: crypto.mode.ECB,
        padding: crypto.pad.Pkcs7,
      });
    };

    // 解密
    export const deCryptoWord = (data) => {
      const keyHex = crypto.enc.Utf8.parse('WDA20201111');
      let decryptedData = crypto.DES.decrypt(data, keyHex, {
        mode: crypto.mode.ECB,
        padding: crypto.pad.Pkcs7,
      });
      return JSON.parse(decryptedData.toString(crypto.enc.Utf8));
    };

    // js 内置uuid生成唯一字符串方法
    // crypto.randomUUID()
    const _uuid = window.crypto.randomUUID();

    // ==========报错处理==========
    try {
    } catch (error) {
      window.location.replace('https://stackoverflow.com/search?q=[js]+' + error.message);
    }

    let obj = {
      a: {
        b: { c: '深度解析obj属性' },
      },
    };
    /**
     * 深度解析obj属性
     */
    function resolveObjPath(path, target) {
      let i = 0,
        len = tempArray.length;
      const tempArray = path.split('.');
      while (i < len) {
        target = target[tempArray[i]];
        i++;
      }
      return target;
    }
    // resolveObjPath('a.b.c', obj);

    // rem函数
    (function (doc, win) {
      var docEl = doc.documentElement,
        resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
        recalc = function () {
          var clientWidth = docEl.clientWidth;
          if (!clientWidth) return;
          docEl.style.fontSize = 37.5 * (clientWidth / 375) + 'px';

          // 安卓微信中字体调大后适配
          if (!doc.body) return;
          var d = doc.createElement('div');
          d.style.cssText = 'width:10rem;height:0;overflow: hidden;position:absolute;z-index:-1;visibility: hidden;';
          doc.body.appendChild(d);
          var dw = d.offsetWidth; // 1rem的实际展示px值
          doc.body.removeChild(d);
          var fz = 10 * (37.5 * (clientWidth / 375)); //正常计算出来的rem基准值 , 可自行修改为rem计算好的值
          var realRem = fz;
          if (dw !== fz) {
            //不相等 则被缩放了
            realRem = Math.pow(fz, 2) / dw / 10;
            docEl.style.fontSize = realRem + 'px';
          }
        };
      recalc();
      if (!doc.addEventListener) return;
      win.addEventListener(resizeEvt, recalc, false);
      doc.addEventListener('DOMContentLoaded', recalc, false);
    })(document, window);

    // vue 分片渲染
    const useDefer = (maxFrameCount = 1000) => {
      let frameCount = 0;
      const refreshFrameCount = () => {
        window.requestAnimationFrame(() => {
          frameCount++;
          if (frameCount < maxFrameCount) {
            refreshFrameCount();
          }
        });
      };
      refreshFrameCount();

      return function (showInFrameCount) {
        return frameCount >= showInFrameCount;
      };
    };

    // 最好的代理单例模式
    function propxySingle(classFn) {
      let ins;
      return new Proxy(classFn, {
        construct(target, args) {
          if (!ins) {
            ins = new target(...args);
          }
          return ins;
        },
      });
    }

    // js实现函数重载
    function addMethod(obj, name, fn) {
      const oldFn = obj[name];
      obj[name] = function (...args) {
        if (args.length === fn.length) {
          return fn.apply(this, args);
        } else if (typeof oldFn === 'function') {
          return oldFn.apply(this, args);
        }
      };
    }
  </script>

  <script type="module">
    import { Mypromise } from './promise.js';
    const p = new Mypromise((resolve, reject) => {
      console.log('Mypromise');
      resolve('then1');
    });

    p.then((res) => {
      console.log('res1', res);
      return 999;
    }).then((res) => {
      console.log('res2', res);
    });

    const testLineXY = [
      [1, 2, 3, 'a'],
      [4, 5, 6, 'b'],
      [7, 8, 9, 'c'],
    ];
    // 交换矩阵的XY轴
    function transformXY(arr) {
      return arr[0].map((_, index) => {
        return arr.map((row) => row[index]);
      });
    }

    try {
      new URL(location.href).searchParams
    } catch (error) {
      console.error('解析URL失败',);
    }
    //  url参数字符串转换为对象，
    Object.fromEntries(new URLSearchParams(location.search).entries());
    //  对象转化为url参数字符串
    new URLSearchParams({ a: 1, b: 2 }).toString();







    /**
     * 执行耗时任务
     * @param task
     * @param cb
     */
    export function _runTask(task, cb) {
      if (window.requestIdleCallback !== undefined) {
        requestIdleCallback((idle) => {
          if (idle.timeRemaining() > 0) {
            task();
            cb?.();
          } else {
            _runTask(task, cb);
          }
        });
      } else {
        const start = Date.now();
        requestAnimationFrame(() => {
          if (Date.now() - start < 16.7) {
            task();
            cb?.();
          } else {
            _runTask(task, cb);
          }
        });
      }
    }
  </script>
</body>

</html>